#ifndef WINAPIKERNEL_H
#define WINAPIKERNEL_H

#include <windows.h>
#include <algorithm>

#include "../../QueryDataManipulation/src/QueryDataManipulation.hpp"
#include "../../DMMath/src/DMMath.hpp"


using namespace KDM::math;

typedef long int Index;

class IKernelThread
{
public:

	virtual ~IKernelThread(){}

	virtual void run() = 0;
	virtual void join() = 0;
};

template<typename DataIterator> class WhereThread : public IKernelThread
{
public:

	WhereThread<DataIterator>(ResultVector& resultVector, const Index leftBorder, const Index rightBorder, WhereFunction isRequired, Index& eraseBorder) 
		: _threadParameters(resultVector, leftBorder, rightBorder, isRequired, eraseBorder){}

	  void run()
	  {
		  _hThread = CreateThread(NULL, 0, WhereThreadFunc, &_threadParameters,0, &_dwThreadId);
	  }
	  void join()
	  {
		  WaitForSingleObject(_hThread, INFINITE);
		  CloseHandle(_hThread);
	  }

private:

	struct ThreadParameters
	{
		std::vector<DataIterator>& resultVector;
		const Index leftBorder;
		const Index rightBorder;
		WhereFunction isRequired;
		Index& eraseBorder;

		ThreadParameters(std::vector<DataIterator>& rVector, const Index lBorder, const Index rBorder, WhereFunction isReq, Index& eBorder) :
		resultVector(rVector), leftBorder(lBorder), rightBorder(rBorder), isRequired(isReq), eraseBorder(eBorder) {}
	};

	static DWORD WINAPI WhereThreadFunc(LPVOID arg)
	{
		struct ThreadParameters* parameters = static_cast<ThreadParameters*>(arg);
		KDM::math::search(parameters->resultVector, parameters->leftBorder, parameters->rightBorder, parameters->isRequired, parameters->eraseBorder);
		return 0;
	}

	struct ThreadParameters _threadParameters;

	HANDLE _hThread;
	DWORD _dwThreadId;
};

template<typename DataIterator, typename SelectType = DataIterator> class SelectThread : public IKernelThread
{
public:

	SelectThread<DataIterator, SelectType>(const ResultVector& resultVector, SelectVector& selectVector, const Index leftBorder, const Index rightBorder, SelectFunction selectFunction) 
		: _threadParameters(resultVector, selectVector, leftBorder, rightBorder, selectFunction){}

	void run()
	{
		_hThread = CreateThread(NULL, 0, SelectThreadFunc, &_threadParameters,0, &_dwThreadId);
	}
	void join()
	{
		WaitForSingleObject(_hThread, INFINITE);
		CloseHandle(_hThread);
	}

private:

	struct ThreadParameters
	{
		const std::vector<DataIterator>& resultVector;
		SelectVector& selectVector;
		const Index leftBorder;
		const Index rightBorder;
		SelectFunction selectFunction;

		ThreadParameters(const std::vector<DataIterator>& rVector, SelectVector& sVector, const Index lBorder, const Index rBorder, SelectFunction sFunction) :
		resultVector(rVector), selectVector(sVector), leftBorder(lBorder), rightBorder(rBorder), selectFunction(sFunction) {}
	};

	static DWORD WINAPI SelectThreadFunc(LPVOID arg)
	{
		struct ThreadParameters* parameters = static_cast<ThreadParameters*>(arg);
		KDM::math::select(parameters->resultVector, parameters->selectVector, parameters->leftBorder, parameters->rightBorder, parameters->selectFunction);
		return 0;
	}

	struct ThreadParameters _threadParameters;

	HANDLE _hThread;
	DWORD _dwThreadId;
};

template<typename DataIterator> class ExecuteThread : public IKernelThread
{
public:

	ExecuteThread<DataIterator>(std::vector<DataIterator>& resultVector, ExecuteFunction executeFunction, int threadNumber) 
		: _threadParameters(resultVector, executeFunction, threadNumber){}

	void run()
	{
		_hThread = CreateThread(NULL, 0, ExecuteThreadFunc, &_threadParameters,0, &_dwThreadId);
	}
	void join()
	{
		WaitForSingleObject(_hThread, INFINITE);
		CloseHandle(_hThread);
	}

private:

	struct ThreadParameters
	{
		std::vector<DataIterator>& resultVector;
		ExecuteFunction executeFunction;
		int threadNumber;

		ThreadParameters(std::vector<DataIterator>& rVector, ExecuteFunction eFunction, int tNumber) :
		resultVector(rVector), executeFunction(eFunction), threadNumber(tNumber) {}
	};

	static DWORD WINAPI ExecuteThreadFunc(LPVOID arg)
	{
		struct ThreadParameters* parameters = static_cast<ThreadParameters*>(arg);
		parameters->executeFunction(parameters->resultVector, parameters->threadNumber);
		return 0;
	}

	struct ThreadParameters _threadParameters;

	HANDLE _hThread;
	DWORD _dwThreadId;
};

template<typename DataIterator> class HoarSortStepThread : public IKernelThread
{
public:

	HoarSortStepThread<DataIterator>(std::vector<DataIterator>& resultVector, const Index leftBorder, const Index rightBorder, OrderByFunction isNeedChange, Index& terminator, DataIterator& median) 
		: _threadParameters(resultVector, leftBorder, rightBorder, isNeedChange, terminator, median){}

	void run()
	{
		_hThread = CreateThread(NULL, 0, HoarSortStepThreadFunc, &_threadParameters,0, &_dwThreadId);
	}
	void join()
	{
		WaitForSingleObject(_hThread, INFINITE);
		CloseHandle(_hThread);
	}

private:

	struct ThreadParameters
	{
		std::vector<DataIterator>& resultVector;
		const Index leftBorder;
		const Index rightBorder;
		OrderByFunction isNeedChanged;
		Index& terminator;
		DataIterator& median;

		ThreadParameters(std::vector<DataIterator>& rVector, const Index lBorder, const Index rBorder, OrderByFunction isNeedCh, Index& term, DataIterator& med) :
		resultVector(rVector), leftBorder(lBorder), rightBorder(rBorder), isNeedChanged(isNeedCh), terminator(term), median(med) {}
	};

	static DWORD WINAPI HoarSortStepThreadFunc(LPVOID arg)
	{
		struct ThreadParameters* parameters = static_cast<ThreadParameters*>(arg);
		KDM::math::hoarSortStep(parameters->resultVector, parameters->leftBorder, parameters->rightBorder, parameters->isNeedChanged, parameters->terminator, parameters->median);
		return 0;
	}

	struct ThreadParameters _threadParameters;

	HANDLE _hThread;
	DWORD _dwThreadId;
};

template<typename DataIterator> class HoarSortSegmentThread : public IKernelThread
{
public:

	HoarSortSegmentThread<DataIterator>(std::vector<DataIterator>& resultVector, const Index leftBorder, const Index rightBorder, OrderByFunction isNeedChange) 
		: _threadParameters(resultVector, leftBorder, rightBorder, isNeedChange){}

	void run()
	{
		_hThread = CreateThread(NULL, 0, HoarSortSegmentFunc, &_threadParameters,0, &_dwThreadId);
	}
	void join()
	{
		WaitForSingleObject(_hThread, INFINITE);
		CloseHandle(_hThread);
	}

private:

	struct ThreadParameters
	{
		std::vector<DataIterator>& resultVector;
		const Index leftBorder;
		const Index rightBorder;
		OrderByFunction isNeedChanged;

		ThreadParameters(std::vector<DataIterator>& rVector, const Index lBorder, const Index rBorder, OrderByFunction isNeedCh) :
		resultVector(rVector), leftBorder(lBorder), rightBorder(rBorder), isNeedChanged(isNeedCh) {}
	};

	static DWORD WINAPI HoarSortSegmentFunc(LPVOID arg)
	{
		struct ThreadParameters* parameters = static_cast<ThreadParameters*>(arg);
		KDM::math::sortSegment(parameters->resultVector, parameters->leftBorder, parameters->rightBorder, parameters->isNeedChanged);
		return 0;
	}

	struct ThreadParameters _threadParameters;

	HANDLE _hThread;
	DWORD _dwThreadId;
};

template<typename DataIterator, typename SelectType = DataIterator> class WinApiKernel : public QDM::IKernel<DataIterator, SelectType>
{
public:
	WinApiKernel() : _countThreads(1) {}

	virtual void where(ResultVector& resultVector, WhereFunction isRequired) const
	{
    std::set<Index> searchSegments = KDM::math::getThreadSegments(resultVector, _countThreads);

    const Index countThreads = (Index)searchSegments.size() - 1;

    std::vector<Index> filterBorders(countThreads);
    std::set<Index>::iterator searchBorder = searchSegments.begin();

		std::vector<IKernelThread*> searchThreads(countThreads);
		for (Index i = 0; i < countThreads; i++)
		{
			Index leftBorder = *searchBorder++;
			Index rightBorder = *searchBorder;
			searchThreads[i] = new WhereThread<DataIterator>(resultVector, leftBorder, rightBorder, isRequired, filterBorders[i]);
			searchThreads[i]->run();
		}

		for (Index i = 0; i < countThreads; i++)
		{
			searchThreads[i]->join();
			delete searchThreads[i];
		}

    KDM::math::eraseExcessElements(resultVector, filterBorders, searchSegments);
	}

	virtual void orderBy(ResultVector& resultVector, OrderByFunction isNeedChange) const
	{
    Index resultVectorSize = (Index)resultVector.size();
    Index left = 0, right = resultVectorSize;

    if (left < right)
    {
      Index segmentsCount = KDM::math::getOrderBySegmentsCount(_countThreads, resultVectorSize);

			std::set<Index> sortBordersSegments;

			sortBordersSegments.insert(left);
			sortBordersSegments.insert(right);

			Index countThreads;

			for (Index i = 1; i < segmentsCount; i *= 2)
			{
				countThreads = (Index)sortBordersSegments.size() - 1;

				std::vector<Index> terminators(countThreads);
				std::vector<IKernelThread*> sortThreads(countThreads);

				std::set<Index>::iterator border = sortBordersSegments.begin();
				std::vector<DataIterator> medians(countThreads);
				for (Index j = 0; j < countThreads; j++)
				{
					Index leftBorder = *border++;
					Index rightBorder = *border;

					sortThreads[j] = new HoarSortStepThread<DataIterator>(resultVector, leftBorder, rightBorder, isNeedChange, terminators[j], medians[j]);
					sortThreads[j]->run();
				}

				for (Index j = 0; j < countThreads; j++)
				{
					sortThreads[j]->join();
					delete sortThreads[j];
				}

				for (Index j = 0; j < countThreads; j++)
				{
					sortBordersSegments.insert(terminators[j]);
				}
			}

			std::set<Index>::iterator sortBorder = sortBordersSegments.begin();

			countThreads = (Index)sortBordersSegments.size() - 1;

			std::vector<IKernelThread*> sortThreads(countThreads);

			for (Index i = 0; i < countThreads; i++)
			{
				Index leftBorder = *sortBorder++;
				Index rightBorder = *sortBorder;
				sortThreads[i] = new HoarSortSegmentThread<DataIterator>(resultVector, leftBorder, rightBorder, isNeedChange);
				sortThreads[i]->run();
			}

			for (Index i = 0; i < countThreads; i++)
			{
				sortThreads[i]->join();
				delete sortThreads[i];
			}
		}
	}

	virtual void select(const ResultVector& resultVector, SelectVector& resultSelect, SelectFunction selectFunction) const
	{
		std::set<Index> selectSegments;

		Index separaterIndex = 0;
		Index countThreads = _countThreads;
		Index resultVectorSize = (Index)resultVector.size();

		resultSelect.resize(resultVectorSize);

		if (_countThreads > resultVectorSize)
		{
			countThreads = resultVectorSize;
		}

		for (Index i = 0; i < (countThreads + 1); i++)
		{
			if (i == countThreads) 
				separaterIndex = resultVectorSize;
			else 
				separaterIndex = i * (resultVectorSize / countThreads);

			selectSegments.insert(separaterIndex);
		}

		resultSelect.resize(resultVectorSize);

		std::set<Index>::iterator selectSegmentsIterator = selectSegments.begin();

		std::vector<Index> borders;

		borders.resize(countThreads + 1);

		for (Index i = 0; i < countThreads + 1; i++)
		{
			borders[i] = *selectSegmentsIterator++;
		}

		std::vector<IKernelThread*> selectThreads(countThreads);
		for (Index i = 0; i < countThreads; i++)
		{
			const Index leftBorder = borders[i];
			const Index rightBorder = borders[i + 1];
			selectThreads[i] = new SelectThread<DataIterator, SelectType>(resultVector, resultSelect, leftBorder, rightBorder, selectFunction);
			selectThreads[i]->run();
		}

		for (Index i = 0; i < countThreads; i++)
		{
			selectThreads[i]->join();
			delete selectThreads[i];
		}
	}

	virtual void execute(ResultVector& resultVector, ExecuteFunction executeFunction) const
	{
		std::vector<IKernelThread*> executeThreads(_countThreads);
		for (Index i = 0; i < _countThreads; i++)
		{
			executeThreads[i] = new ExecuteThread<DataIterator>(resultVector, executeFunction, i);
			executeThreads[i]->run();
		}
		for (Index i = 0; i < _countThreads; i++)
		{
			executeThreads[i]->join();
			delete executeThreads[i];
		}
	}

	void setCountThreads(Index countThreads){_countThreads = countThreads;}
	Index getCountThreads() const {return _countThreads;}

private:
	Index _countThreads;
};


#endif